/************************************************************************/
/*Copyright (c) James Zhao 2007,2008
/*This file is part of AdminOverlay.
/*AdminOverlay is free software: you can redistribute it and/or modify
/*it under the terms of the GNU General Public License as published by
/*the Free Software Foundation, either version 3 of the License, or
/*(at your option) any later version.
/*
/*AdminOverlay is distributed in the hope that it will be useful,
/*but WITHOUT ANY WARRANTY; without even the implied warranty of
/*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
/*GNU General Public License for more details.
/*
/*You should have received a copy of the GNU General Public License
/*along with AdminOverlay.  If not, see <http://www.gnu.org/licenses/>.                                                                     */
/*************************************************************************/
#include "InputManager.h"
#include "GameOverlay.h"

#define DESTROY_MESSAGE(msg) msg->message = 0;

#define GETDEVICESTATE_OFFSET_WINXP 32935
#define GETDEVICEDATA_OFFSET_WINXP 33403

#define GETDEVICESTATE_OFFSET_WINVISTA 31897
#define GETDEVICEDATA_OFFSET_WINVISTA 32370

InputManager *InputManager::currentInstance = NULL;

HRESULT (__stdcall *InputManager::GetDeviceData_Trampoline)
(LPDIRECTINPUTDEVICE8, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD);
HRESULT (__stdcall *InputManager::GetDeviceState_Trampoline)
(LPDIRECTINPUTDEVICE8, DWORD, LPVOID);

InputManager::InputManager(GameOverlay *overlay)
{
	InputManager::currentInstance = this;
	deliverMessageWithoutHook = false;
	isCtrlDown  = false;
	this->overlay = overlay;
	activationKey = OverlayConfig::currentInstance()->activationKey;
	mousePos.x = -1;
	mousePos.y = -1;
	isStarted = false;
}

InputManager::~InputManager(void)
{
}

inline bool InputManager::getMessageHandler( int code, WPARAM wp, LPARAM lp )
{
	if(!isStarted)
	{
		return true;
	}
	MSG *msg = (MSG*)lp;
	UINT msgCode = msg->message;
	LPARAM lParam = msg->lParam;
	WPARAM wParam = msg->wParam;
	if(msgCode == WM_ACTIVATE)
	{
		msgCode = 0;
	}
 	else if(msgCode == WM_MOUSEMOVE)
	{
		return onMouseMove_Msg(msg);
	}
	else if(msgCode == WM_LBUTTONDOWN)
	{
		return onMouseDown_Msg(msg);
	}
	else if(msgCode == WM_LBUTTONUP)
 	{
		return onMouseUp_Msg(msg);
 	}
 	else if (msgCode == WM_KEYDOWN)
	{
		return onKeyDown_Msg(msg);
	}
	else if (msgCode == WM_KEYUP)
	{
		return onKeyUp_Msg(msg);
	}
	else
	{
		//do nothing for all misc msgs
		return true;
	}	
	//prevent that annoying warning msg
	return true;
}

void InputManager::initalize( HINSTANCE instance, DWORD threadID )
{
	getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)getMSGProc,
		instance, threadID);
}
bool InputManager::onKeyDown_Msg( MSG *msg )
{
	int keyCode = (int)msg->wParam;
	if(keyCode == activationKey && isCtrlDown == true)
	{
		if(overlay->IsActive() == true)		
		{
			overlay->IsActive(false);
			deliverMessageWithoutHook = true;			
			return false;
		}
		else
		{
			overlay->IsActive(true);
			deliverMessageWithoutHook = false;
			return false;
		}
	}
	if(keyCode == VK_CONTROL)
	{
		isCtrlDown = true;
	}
	//optimization
	if(deliverMessageWithoutHook)
	{
		return true;
	}
	else
	{
		CEGUI::System::getSingleton().
			injectKeyDown(msg->wParam);
		CEGUI::System::getSingleton().
			injectChar(msg->wParam);
		return false;
	}
}

bool InputManager::onKeyUp_Msg( MSG *msg )
{
	int keyCode = (int)msg->wParam;
	
	if(keyCode == VK_CONTROL)
	{
		isCtrlDown = false;
	}
	//optimization
	if(deliverMessageWithoutHook)	
	{
		return true;
	}
	else
	{
		CEGUI::System::getSingleton().
			injectKeyUp(msg->wParam);
		return false;
	}
}

bool InputManager::onMouseMove_Msg( MSG *msg )
{
	mousePos = MAKEPOINTS(msg->lParam);
	if(deliverMessageWithoutHook)
	{
		return true;
	}
	else
	{
		CEGUI::System::getSingleton().
			injectMousePosition(mousePos.x, mousePos.y);
		return false;
	}
}

bool InputManager::onMouseDown_Msg( MSG *msg )
{
	if(deliverMessageWithoutHook)
	{
		return true;
	}
	else
	{
		CEGUI::System::getSingleton().
			injectMouseButtonDown(CEGUI::LeftButton);
		return false;
	}
}

bool InputManager::onMouseUp_Msg( MSG *msg )
{
	if(deliverMessageWithoutHook)
	{
		return true;
	}
	else
	{

		CEGUI::System::getSingleton().
			injectMouseButtonUp(CEGUI::LeftButton);
		return true;
	}
}




HRESULT __stdcall InputManager::My_GetDeviceData
	(LPDIRECTINPUTDEVICE8 device, DWORD param_dataSize, LPDIDEVICEOBJECTDATA param_objData,
	LPDWORD param_elementCount, DWORD param_flags)
{
	//keyboard
	if(currentInstance->deliverMessageWithoutHook == true)
	{
		return GetDeviceData_Trampoline(device, param_dataSize, param_objData,
			param_elementCount, param_flags);
	}
	else
	{
		DWORD dwItems = INFINITE; 
		*param_elementCount = 0;
		return GetDeviceData_Trampoline( device, sizeof(DIDEVICEOBJECTDATA), NULL, &dwItems, 0); 
	}
}

HRESULT __stdcall InputManager::My_GetDeviceState
	( LPDIRECTINPUTDEVICE8 device, DWORD dataSize, LPVOID data)
{
	//mouse
	if(currentInstance->deliverMessageWithoutHook == true)
	{
		return GetDeviceState_Trampoline(device, dataSize, data);		
	}
	else
	{
		static DIMOUSESTATE mousestate;
		return GetDeviceState_Trampoline(device, sizeof(DIMOUSESTATE), (LPVOID)&mousestate);
		
	}
}


void InputManager::setupGetDeviceDataDetours(int offset)
{
	PBYTE real_GetDeviceData = (PBYTE)reinterpret_cast<void*>(
		reinterpret_cast<ptrdiff_t>(GetModuleHandle(TEXT("dinput8.dll"))) + offset);
	//make sure the real endscene is there
	if(!IsBadReadPtr(real_GetDeviceData, 1))
	{
		InputManager::GetDeviceData_Trampoline = 
			(HRESULT (__stdcall *)
			(LPDIRECTINPUTDEVICE8, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD))
			DetourFunction((PBYTE)real_GetDeviceData, (PBYTE)InputManager::My_GetDeviceData);
	}
}

void InputManager::setupGetDeviceStateDetours(int offset)
{
	PBYTE real_GetDeviceState = (PBYTE)reinterpret_cast<void*>(
		reinterpret_cast<ptrdiff_t>(GetModuleHandle(TEXT("dinput8.dll"))) + offset);
	//make sure the real endscene is there
	if(!IsBadReadPtr(real_GetDeviceState, 1))
	{
		InputManager::GetDeviceState_Trampoline = 
			(HRESULT (__stdcall *)
			(LPDIRECTINPUTDEVICE8, DWORD, LPVOID))
			DetourFunction((PBYTE)real_GetDeviceState, (PBYTE)InputManager::My_GetDeviceState);
	}

}

void InputManager::setupDetours()
{
	LoadLibrary(TEXT("dinput8.dll"));
	if(GetModuleHandle(TEXT("dinput8.dll")) != NULL)
	{	
		OSVERSIONINFO version_info;
		version_info.dwOSVersionInfoSize = sizeof(version_info);
		GetVersionEx(&version_info);

		if (version_info.dwMajorVersion == 5) 
		{
			setupGetDeviceStateDetours(GETDEVICESTATE_OFFSET_WINXP);
			setupGetDeviceDataDetours(GETDEVICEDATA_OFFSET_WINXP);
		}
		else if (version_info.dwMajorVersion == 6) 
		{
			setupGetDeviceStateDetours(GETDEVICESTATE_OFFSET_WINVISTA);
			setupGetDeviceDataDetours(GETDEVICEDATA_OFFSET_WINVISTA);
		}
		
	}
}


POINT InputManager::MakePoint( LPARAM param )
{
	POINT p;
	p.x = (GET_X_LPARAM(param));
	p.y = (GET_Y_LPARAM(param));
	return p;
}
LRESULT CALLBACK getMSGProc(int code, WPARAM wParam, LPARAM lParam)
{
	bool msgHandleResult = InputManager::currentInstance->
		getMessageHandler(code, wParam, lParam);
	if(msgHandleResult == true)
	{
		return CallNextHookEx(getMessageHook, code, wParam, lParam);
	}
	else
	{
		//make the calle ignore the message
		((MSG*)lParam)->message = WM_NULL;
		return 0;
	}
}
